#ifndef __AXES_HH__
#define __AXES_HH__

#include <misc/dictsortedvector.h>
#include <misc/dictvector.h>
#include <dyntype/dynobject.h>

typedef size_t axis_index_t;
typedef size_t data_index_t;

class Axis
{
 public:
  Axis(const string& axis_name, DynType* dyntype);
  virtual ~Axis();
  const string& get_name() const;
  DynType* get_tics_dyntype() const;
  
  size_t get_size() const;  
  void get_tics(vector<DynObject>* ret_tics) const {
    for (size_t i = 0; i < tics.size(); i++) {
	ret_tics->push_back(tics.key_at(i));
    }
  }

  // return true if element was added
  bool add_tic(const DynObject& tic, data_index_t data_index);
  void remove_tic(axis_index_t axis_index);
  // return true if element was found
  bool find_tic(const DynObject& tic,
		axis_index_t* axis_index,
		data_index_t* data_index) const;

  template <class TIC_T>
    bool add_tic(const TIC_T& tic, data_index_t data_index) {
    DynObject o(get_dyntype<TIC_T>(), membuf_t((char*)&tic, sizeof tic));
    return this->Axis::add_tic(o, data_index);
  }

  void print(ostream& out) const;
  void save(ostream& out) const;
  void load(istream& in);

 protected:
  friend class Axes;
  friend class AxesCoor;
  string axis_name;
  DynType* tics_dyntype;
  DictSortedVector<DynObject, data_index_t> tics;
};

class AxesCoor;
class AxesRange;
class Axes 
{
 public:
  vector<axis_index_t> get_shape() const;
  size_t get_n_dim() const;
  void add_axis(const string& axis_name, Axis* axis);
  Axis* get(const string& axis_name) const;
  Axis* get_at(size_t axis_index) const;

  template <typename TIC_T>
    void add_axis(const string& axis_name) {
    Axis* new_axis = new Axis(axis_name, get_dyntype<TIC_T>());
    this->add_axis(axis_name, new_axis);
  }

  bool does_coor_exist(const AxesCoor&) const;
  vector<axis_index_t> get_axis_indexes_coor(const AxesCoor&) const;
  vector<data_index_t> 
      get_data_indexes_coor(const vector<axis_index_t>&) const;
  vector<pair<axis_index_t, axis_index_t> > 
    get_axis_indexes_range(const AxesRange&) const;

  void dump() const;
  void print(ostream& out) const;
  void save(ostream& out) const;
  void load(istream& in);

 private:
  DictVector<string, Axis*> axes;
};

class AxesCoor {
public:
  AxesCoor(const Axes* parent_axes);
  AxesCoor(const Axes* parent_axes, const vector<axis_index_t>& axis_indexes_coor);

  size_t get_n_dim() const;

  DynObject get_at(size_t i_dim) const;
  DynObject get(const string& dim) const;

  void set_at(size_t i_dim, const DynObject& v);
  void set(const string& dim, const DynObject& v);

  template <class TIC_T> void set_at(size_t i_dim, const TIC_T& tic)
  {
    DynType* dt = tics.get_at(i_dim).get_dyntype();
    if (dt != get_dyntype<TIC_T>()) {
      ostringstream msg;
      msg << "AxesCoor::set_at: dyntype mismatch, "
	  << dt->get_name()
	  << " " << get_dyntype<TIC_T>()->get_name();
      throw Error(__FILE__, __LINE__, msg.str());
    }
    DynObject tic_o(dt, membuf_t((char*)&tic, sizeof(tic)));
    tics.set_at(i_dim, tic_o);
  }

  template <class TIC_T> void set(const string& dim, const TIC_T& tic)
  {
    size_t i_dim = 0;
    if (tics.find(dim, &i_dim) == false) {
      throw Error(__FILE__, __LINE__, "AxesCoor::set: can't find axes dim " + dim);
    }
    this->set_at<TIC_T>(i_dim, tic);
  }

  template <class TIC_T> TIC_T get_at(size_t i_dim) const
  {
    DynType* dt = tics.get_at(i_dim).get_dyntype();
    if (dt != get_dyntype<TIC_T>()) {
      throw Error(__FILE__, __LINE__, "AxesCoor::get_at: dyntype mismatch");
    }
    const DynObject& ret_buf = tics.get_at(i_dim);
    TIC_T t = ret_buf.get_value<TIC_T>();
    return t;
  }

  template <class TIC_T> TIC_T get(const string& dim) const
  {
    size_t i_dim = 0;
    if (tics.find(dim, &i_dim) == false) {
      throw Error(__FILE__, __LINE__, "AxesCoor::get: can't find axes dim " + dim);
    }
    return get_at<TIC_T>(i_dim);
  }
 
  void print(ostream& out) const;

private:
  DictVector<string, DynObject> tics;
};

class AxesRange
{
 public:
  const Axes* axes;
  AxesRange(const Axes* axes);

  size_t get_n_dim() const;
  void set_left(const string& dim, const DynObject&);
  void set_right(const string& dim, const DynObject&);

  void dump() const;
  void print(ostream&) const;

  vector<pair<axis_index_t, axis_index_t> > get_axis_indexes_range() const;
  DictVector<string, pair<DynObject, DynObject> > tic_ranges;
};

#endif
